﻿using System;
using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.Linq;
using System.Text;
using System.Windows;
using DocumentFormat.OpenXml.Office2010.PowerPoint;
using DocumentFormat.OpenXml.Packaging;
using DocumentFormat.OpenXml.Presentation;
using OpenPowerPoint.Collection;

namespace OpenPowerPoint.Slides
{
    /// <summary>
    /// Represents a group of slides
    /// </summary>
    public class SlideGroup : DependencyObject, INotify
    {
        #region Load/Save
        /// <summary>
        /// Generates a new Instace out of the slide group
        /// </summary>
        /// <param name="section">The section to load the group from</param>
        /// <param name="presentation">The presentation to load the child slides out of</param>
        /// <returns>A slideGroup Instance according to the Section</returns>
        public static SlideGroup Load(Section section, PresentationPart presentation)
        {
            var ret = new SlideGroup();
            ret.Name = section.Name;

            int slidecount = section.SectionSlideIdList.Elements<SectionSlideIdListEntry>().Count();
            var children = new List<SlidePart>();

            for (int i = 0; i < slidecount; i++)
            {
                SectionSlideIdListEntry slide = section.SectionSlideIdList.Elements<SectionSlideIdListEntry>().ElementAt(i);

                children.Add(RelationResolver.GetSlideById(slide.Id, presentation));
            }

            AddChildren(ref ret, children);

            return ret;
        }

        /// <summary>
        /// Generates a new Instance with a name and children
        /// </summary>
        /// <param name="name">The caption of the group</param>
        /// <param name="children">The children of the group</param>
        /// <returns>A slideGroup Instance with the given caption and children</returns>
        public static SlideGroup Load(string name, IEnumerable<SlidePart> children)
        {
            var ret = new SlideGroup();
            ret.Name = name;

            AddChildren(ref ret, children);

            return ret;
        }

        /// <summary>
        /// Saves the group and its content into the presentation part
        /// </summary>
        /// <param name="presentation">The presentation to write into</param>
        /// <param name="sections">The sectionList to save the section into</param>
        public void Save(ref PresentationPart presentation, ref SectionList sections)
        {
            if (Name != "default")
            {
                var section = new Section { Name = Name, Id = Guid.NewGuid().ToString() };
                var ids = new SectionSlideIdList();

                //Append all child slides to the current group
                foreach (var slide in Slides)
                {
                    section.SectionSlideIdList.Append(slide.Save(ref presentation));
                }

                sections.Append(section);
            }
            else
            {
                foreach (var slide in Slides)
                    slide.Save(ref presentation);
            }
        }

        /// <summary>
        /// Performs a cleanup of all temporary data
        /// </summary>
        public void Saved()
        {
            foreach (var slide in Slides)
            {
                slide.Saved();
            }
        }
        #endregion

        #region Initializer
        /// <summary>
        /// An initializer for private use only
        /// </summary>
        /// <remarks>This initializer is private, because we don't need arguments for the <see cref="Load"/> mechanism and can be sure, that <see cref="Load"/> will do the rest for us.<seealso cref="Load"/></remarks>
        private SlideGroup()
        {
            Slides = new NotifyableList<Slide>();
        }

        /// <summary>
        /// Initializes a new SlideGroup
        /// </summary>
        /// <param name="name">The caption of the slide group</param>
        /// <param name="children">The children of the slide group</param>
        public SlideGroup(string name, IEnumerable<Slide> children)
            : this()
        {
            Name = name;

            ((NotifyableList<Slide>)Slides).AddRange(children);
        }
        #endregion

        #region properties
        public static readonly DependencyProperty SlidesProperty =
            DependencyProperty.Register("Slides", typeof(NotifyableList<Slide>), typeof(SlideGroup),
                                        new PropertyMetadata(default(NotifyableList<Slide>),
                                                             (o, e) =>
                                                             {
                                                                 if (e.NewValue != null)
                                                                     ((NotifyableList<Slide>)e.NewValue).PropertyChanged +=
                                                                         ((SlideGroup)o).OnPropertyChanged;
                                                                 if (e.OldValue != null)
                                                                     ((NotifyableList<Slide>)e.OldValue).PropertyChanged -=
                                                                         ((SlideGroup)o).OnPropertyChanged;
                                                             }));

        public ObservableCollection<Slide> Slides
        {
            get { return (ObservableCollection<Slide>)GetValue(SlidesProperty); }
            set { SetValue(SlidesProperty, value); }
        }

        public static readonly DependencyProperty NameProperty =
            DependencyProperty.Register("Name", typeof(string), typeof(SlideGroup), new PropertyMetadata(default(string), (o, e) => ((SlideGroup)o).OnPropertyChanged(o, new SProperty(NameProperty, e.OldValue, e.NewValue))));

        public string Name
        {
            get { return (string)GetValue(NameProperty); }
            set { SetValue(NameProperty, value); }
        }
        #endregion

        #region Functions
        #region Call-Through
        /// <summary>
        /// Sets the name of the instance
        /// </summary>
        /// <param name="name"></param>
        /// <returns>The SlideGroup instance to work with</returns>
        public SlideGroup SetName(string name)
        {
            Name = name;
            return this;
        }
        #endregion

        #region Slides
        /// <summary>
        /// Adds a new slide at the end of the list
        /// </summary>
        /// <returns>The new slide instance</returns>
        public Slide AddSlide()
        {
            Slide ret = new Slide();

            Slides.Add(ret);

            return ret;
        }

        /// <summary>
        /// Adds a new slide at the index or at the end
        /// </summary>
        /// <param name="index">The index to insert the new slide</param>
        /// <returns>The new slide instance</returns>
        /// <exception cref="IndexOutOfRangeException">The index is lower than zero</exception>
        public Slide AddSlide(int index)
        {
            if (index < 0)
                throw new IndexOutOfRangeException("The index has to be greater or equal zero");

            Slide ret = new Slide();

            if (index >= Slides.Count)
                Slides.Add(ret);
            else
                Slides.Insert(index, ret);

            return ret;
        }

        /// <summary>
        /// Gets the slide at the desired position
        /// </summary>
        /// <param name="index">The index</param>
        /// <returns>The slide at the position of index</returns>
        /// <exception cref="IndexOutOfRangeException">The index was not in range of the slides</exception>
        public Slide GetSlide(int index)
        {
            if (index < 0 || index >= Slides.Count)
                throw new IndexOutOfRangeException("The index was out of range");

            return Slides[index];
        }

        /// <summary>
        /// Removes a slide out of the list
        /// </summary>
        /// <param name="index">The index to remove</param>
        /// <exception cref="IndexOutOfRangeException">The index was not in range of the slides</exception>
        public void RemoveSlide(int index)
        {
            if (index < 0 || index >= Slides.Count)
                throw new IndexOutOfRangeException("Index out of range");

            Slides.RemoveAt(index);
        }

        /// <summary>
        /// Removes a slide out of the list
        /// </summary>
        /// <param name="slide">The slide to remove</param>
        /// <exception cref="ArgumentException">The slide is not part of this group</exception>
        public void RemoveSlide(Slide slide)
        {
            if (!Slides.Contains(slide))
                throw new ArgumentException("The slide is not in this group");

            Slides.Remove(slide);
        }

        /// <summary>
        /// Returns the number of slides
        /// </summary>
        /// <returns></returns>
        public int SlideCount()
        {
            return Slides.Count();
        }
        #endregion
        #endregion

        /// <summary>
        /// Adds every submitted child to the group
        /// </summary>
        /// <param name="group">The group to alter</param>
        /// <param name="slides">The slides to add</param>
        private static void AddChildren(ref SlideGroup group, IEnumerable<SlidePart> slides)
        {
            foreach (SlidePart slide in slides)
            {
                group.Slides.Add(Slide.Load(slide));
            }
        }

        #region INotify
        internal event NotifyableList<INotify>.PropertyChangedDelegate PropertyChanged;
        event NotifyableList<INotify>.PropertyChangedDelegate INotify.PropertyChanged
        {
            add { PropertyChanged += value; }
            remove { PropertyChanged -= value; }
        }

        private bool _avoidPropertyChanged;
        private void OnPropertyChanged(object sender, SProperty property)
        {
            if (!_avoidPropertyChanged)
            {
                _avoidPropertyChanged = true;

                NotifyableList<INotify>.PropertyChangedDelegate handler = PropertyChanged;
                if (handler != null) handler(sender, property);

                /* In WPF, we have to set the properties back manually, because we destroy
                 * the reference to the original PropertyChanged Argument submitted in the
                 * ObservableCollection root.
                 */
                
                if (property.Property != null)
                    SetValue(property.Property, property.NewValue);

                _avoidPropertyChanged = false;
            }
        }
        #endregion
    }
}
